package ro.isdc.navigator.impl;

import java.util.List;

import ro.isdc.navigator.IPagesNavigator;
import ro.isdc.navigator.Page;
import ro.isdc.navigator.exceptions.SearchLimitExceededException;
import ro.isdc.navigator.factory.SearchContext;
import ro.isdc.navigator.retriever.IRetriever;

/**
 * A page navigator implementation
 * 
 * @author Andrei Chirila / ISDC! Romania
 * @version 1.0
 * @date Jun 30, 2009 
 * @param <T> the type of the expected results
 */
public class PagesNavigator<T> implements IPagesNavigator<T> {

  /**
   * The retriever to be used
   */
  private IRetriever<T> retriever;

  /**
   * The search context
   */
  private SearchContext searchContext;

  /**
   * The current page number
   */
  private int currentPageNumber;

  /**
   * The number of pages
   */
  private int pageCount = -1;
  
  /**
   * @param searchContext
   *          the searchContext to set
   */
  public void setSearchContext(final SearchContext searchContext) {
    this.searchContext = searchContext;
  }
  
  /**
   * @param retriever the retriever to set
   */
  public void setRetriever(final IRetriever<T> retriever) {
    this.retriever = retriever;
  }

  /**
   * {@inheritDoc}
   */
  public Page<T> getPrevious() throws SearchLimitExceededException {
    if (hasPrevious()) {
      return getPage(currentPageNumber - 1);
    }

    return null;
  }
  
  /**
   * {@inheritDoc}
   */
  public boolean hasPrevious() {
    return currentPageNumber > 1;
  }

  /**
   * {@inheritDoc}
   */
  public Page<T> getNext() throws SearchLimitExceededException {
    if (hasNext()) {
      return getPage(currentPageNumber + 1);
    }

    return null;
  }
  
  /**
   * {@inheritDoc}
   */
  public boolean hasNext() {
    return currentPageNumber < getPagesCount();
  }

  /**
   * {@inheritDoc}
   */
  public int getPagesCount() {
    if (pageCount > 0) {
      return pageCount;
    }

    long numberOfRows = getNumberOfResults();
    int numberOfPages = (int) numberOfRows / searchContext.getPageSize();
    if (numberOfRows % searchContext.getPageSize() > 0) {
      numberOfPages++;
    }

    this.pageCount = numberOfPages;

    return pageCount;
  }

  /**
   * {@inheritDoc}
   */
  public Page<T> getPage(final int pageNumber) throws SearchLimitExceededException {
    if (searchContext.getSearchLimit() > 0
        && (pageNumber - 1) * searchContext.getPageSize()
            + searchContext.getPageSize() > searchContext.getSearchLimit()) {
      throw new SearchLimitExceededException(
          "The search limit has been exceeded. "
              + "You are not allowed to see more data.");
    }

    Page<T> page = new Page<T>();
    page.setPageSize(searchContext.getPageSize());

    List<T> results = getResults(pageNumber);
    page.setResults(results);

    page.setPageNo(pageNumber);
    this.currentPageNumber = pageNumber;

    return page;

  }

  /**
   * Gets the results for a page given by its page number.
   * 
   * @param pageNumber the page number
   * @return the list of results.
   */
  private List<T> getResults(final int pageNumber) {
    return retriever.getResults((pageNumber - 1) * searchContext.getPageSize(),
        searchContext.getPageSize(), searchContext.getArgs());
  }

  /**
   * Computes the number of results in the table.
   * 
   * @return the computed number of results.
   */
  private long getNumberOfResults() {
    return retriever.getNumberOfResults(searchContext.getArgs());
  }
  
}
